﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using FinanSoft.Entities;
using System.Collections;
using FinanSoft.Utilities;
using System.IO;

namespace FinanSoft.BusinessLayer
{
    public class ExternInvoiceManager
    {
        #region Atributos
        private static ExternInvoiceManager _Instance;
        private bool _IsErrorItem;
        private bool _IsErrorDoc;
        private bool _IsError;
        private string _ErrorMessage;
        private Hashtable _CorrectDocuments;
        #endregion


        #region Constructor Singleton
        /// <summary>
        ///  Constructor Privado de al clase
        /// </summary>
        private ExternInvoiceManager()
        {
            _IsErrorItem = false;
            _IsErrorDoc = false;
            _IsError = false;
            _ErrorMessage = "";
            _CorrectDocuments = new Hashtable();
        }

        /// <summary>
        /// Metodo que obtienen la instancia existente de la clase. Si esta aun no ha sido instanciada, la instancia.
        /// </summary>
        public static ExternInvoiceManager GetInstance()
        {
            if (_Instance == null)
                _Instance = new ExternInvoiceManager();
            return _Instance;
        }
        #endregion


        #region Propiedades
        #endregion


        #region Métodos Públicos
        /// <summary>
        /// Método que se encarga de guardar en la base de datos el documento con el código que se especifica
        /// si el código se encuentra entre los documentos que fueron revisados con éxitos, lo guarda sino da error
        /// </summary>
        /// <param name="documentCode">Código del documento que se quiere guardar</param>
        /// <param name="logFileName">Nombre del archivo de resultados</param>
        /// <returns>Retorna si la acción tuvo éxito o no</returns>
        public bool SaveExternInvoice(int documentCode, String logFileName)
        {
            this.WriteToFile(logFileName, Constants.MESSAGE_REGISTER_DOC_BEGIN1 + documentCode + Constants.MESSAGE_REGISTER_DOC_BEGIN2);
            Document document = null;
            // Revisamos si el documento se encuentra entre los documentos ya revisados
            if (_CorrectDocuments.ContainsKey(documentCode))
                document = (Document)_CorrectDocuments[documentCode];
            else
            {
                _IsError = true;
                _ErrorMessage = Constants.MESSAGE_ERROR_DOC_NOT_FOUND;
                // Se escribe en el archivo
                this.WriteToFile(logFileName, Constants.MESSAGE_ERROR_DOC_NOT_FOUND);
                this.WriteToFile(logFileName, Constants.MESSAGE_SEPARATOR);
                return false;
            }

            // Si encontramos el documento entonces lo guardamos en la db
            bool noError = BuySaleManager.GetInstance().SaveDocument(document.tipoDocumento, document.IdCuentaAuxiliar, document.fechaEmision, document.fechaVencimiento, document.IdMoneda, Constants.INVALID_ID, document.subTotal, document.impuestos, document.total, document.detalleDoc.ToList());
            if (!noError)
            {
                _IsError = true;
                _ErrorMessage = BuySaleManager.GetInstance().ErrorMessage;
                // Se escribe en el archivooo
                _CorrectDocuments.Remove(documentCode);
                this.WriteToFile(logFileName, _ErrorMessage);
                this.WriteToFile(logFileName, Constants.MESSAGE_SEPARATOR);
                return false;
            }

            // Luego actualizamos el id del documento insertado por el verdadero
            noError = BuySaleManager.GetInstance().UpdateDocumentId(documentCode);
            if (!noError)
            {
                _IsError = true;
                _ErrorMessage = BuySaleManager.GetInstance().ErrorMessage;
                this.WriteToFile(logFileName, _ErrorMessage);
                this.WriteToFile(logFileName, Constants.MESSAGE_SEPARATOR);
                return false;
            }


            this.WriteToFile(logFileName, Constants.MESSAGE_REGISTER_DOC_SUCCESS);
            this.WriteToFile(logFileName, Constants.MESSAGE_SEPARATOR);

            _CorrectDocuments.Remove(documentCode);
            return true;
        }
        
        /// <summary>
        ///  Método que se encarga de revisar el formato del xml de un documento en específico,
        ///  valida cada uno de los campos que lo componen, y si todos ellos fueron correctos., entonces
        ///  crea un objeto documento y lo guarda en memoria para cuando quiera guardarlo
        /// </summary>
        /// <param name="xmlDoc">Nodo xml del documento a revisar</param>
        /// <param name="logFileName">Nombre del archivo de resultados</param>
        /// <returns>Retorna el código del documento revisado, si es -1 entonces es porque hubo errores</returns>
        public int CheckInvoiceFile(XmlNode xmlDoc, String logFileName)
        {
            _IsErrorDoc = false;
            // Escribo en el archivo inicio de revisión para el documento tal........

            int documentCode = Convert.ToInt32(xmlDoc.Attributes[Constants.TAG_ATT_NAME_DOC_CODE].Value);
            this.WriteToFile(logFileName, Constants.MESSAGE_CHECK_DOC_BEGIN1 + documentCode + Constants.MESSAGE_CHECK_DOC_BEGIN2);
            // Revisamos el código del cliente (cuenta auxiliar)
            String clientCode = xmlDoc.Attributes[Constants.TAG_ATT_NAME_DOC_CLIENT].Value;
            AuxiliarAccount account = AuxiliaryAccountManager.GetInstance().GetAuxiliarAccount(clientCode);
            if (account == null)
            {
                _IsErrorDoc = true;
                // Escribo en el archivo error con el cliente
                this.WriteToFile(logFileName, Constants.MESSAGE_DOC_CLIENT_NOT_FOUND);
                return Constants.INVALID_ID;
            }

            // Verificamos que exista la moneda
            String currencyName = xmlDoc.Attributes[Constants.TAG_ATT_NAME_DOC_CURRENCY].Value;
            Currency cur;
            if (currencyName == Constants.DEFAULT_VALUE)
               cur = MoneyManager.GetInstance().LocalCurrency;
            else
                cur = MoneyManager.GetInstance().GetCurrency(currencyName);
            if (cur == null)
            {
                _IsErrorDoc = true;
                // Escribo en el archivo con sobre la moneda que estuvo mal
                this.WriteToFile(logFileName, Constants.MESSAGE_DOC_CURRENCY_NOT_FOUND);
                return Constants.INVALID_ID;
            }

            // Ahora procedemos a evaluar los items que tiene el documento
            XmlNodeList xmlItems = xmlDoc.ChildNodes;
            List < DocDetail >  details = this.CheckDocItems(xmlItems, logFileName);
            if (details == null)
            {
                // Escribo en el archivo?
                _IsErrorDoc = true;
                this.WriteToFile(logFileName, Constants.MESSAGE_DOC_ITEMS_ERROR);
                return Constants.INVALID_ID;
            }

            DateTime postingDate = Convert.ToDateTime(xmlDoc.Attributes[Constants.TAG_ATT_NAME_DOC_POST_DATE].Value);
            DateTime deliveryExpDate = Convert.ToDateTime(xmlDoc.Attributes[Constants.TAG_ATT_NAME_DOC_DEL_DATE].Value); ;

            if (!_IsErrorDoc)
            {
                double amountBeforeTax = CalculateAmountBeforeTax(details);
                double taxAmount = CalculateTaxAmount(details);
                double totalAmount = amountBeforeTax - taxAmount;

                Document doc = new Document(Constants.FC_ACCOUNTING_ENTRY, account.AccountID, postingDate, deliveryExpDate, cur.Id, Constants.INVALID_ID, amountBeforeTax, taxAmount, totalAmount);
                doc.detalleDoc = details.ToArray();
                _CorrectDocuments.Add(documentCode, doc);
                this.WriteToFile(logFileName, Constants.MESSAGE_CHECK_DOC_END1 + doc.codigoDocumento + Constants.MESSAGE_CHECK_DOC_END2);
                // Escribo finalización de la revisión para el doc
                this.WriteToFile(logFileName, Constants.MESSAGE_CHECK_DOC_END);

                return documentCode;
            }

            // Escribo finalización de la revisión para el doc
            this.WriteToFile(logFileName, Constants.MESSAGE_CHECK_DOC_END);
            return Constants.INVALID_ID;
        }


        
        #endregion


        #region Métodos Privados
        /// <summary>
        /// Método que se encarga de checkear los ítems para un documento en específico
        /// si todo va bien, entonces va creando elementos DocDetail, para que a la hora de
        /// guardar, estén ya en memoria listos
        /// </summary>
        /// <param name="xmlItems">Lista de nodos xml de items</param>
        /// <param name="logFileName">Nombre del archivo en el que se está escribiendo los resultados</param>
        /// <returns>Lista de elementos DocDetail para cada uno de los ítems revisados, si hubo algún error retorna null</returns>
        private List<DocDetail> CheckDocItems(XmlNodeList xmlItems, String logFileName)
        {
            _IsErrorItem = false;
            List<DocDetail> details = new List<DocDetail>();

            foreach (XmlNode itemNode in xmlItems)
            {
                DocDetail detail;
                // Validamos el artículo
                String itemCode = itemNode.Attributes[Constants.TAG_ATT_NAME_ITEM_CODE].Value; ;
                Item item = ItemManager.GetInstance().GetItem(itemCode);
                if (item == null)
                {
                    _IsErrorItem = true;
                    // Escribir en el archivo y setear a false para que no haga el doc
                    this.WriteToFile(logFileName, Constants.MESSAGE_CHECK_ITEM_ID_NOT_FOUND);
                    return null;
                }

                // Validamos la bodega
                String storeRoomName = itemNode.Attributes[Constants.TAG_ATT_NAME_ITEM_ROOM].Value;
                StoreRoom storeRoom = null;
                if (storeRoomName != Constants.DEFAULT_VALUE)
                    storeRoom = StoreRoomManager.GetInstance().GetStoreRoom(storeRoomName);
                else
                    storeRoom = StoreRoomManager.GetInstance().GetDefaultStoreRoom(item);
                if (storeRoom == null)
                {
                    _IsErrorItem = true;
                    // Escribir en el archivo y setear a false para que no haga el doc
                    this.WriteToFile(logFileName, Constants.MESSAGE_CHECK_ITEM_ROOM_NOT_FOUND);
                    return null;
                }

                // Revisar estooo
                double price;
                if (storeRoom != null)
                    price = item.GetStoreRoomInfo(storeRoom.Id).CostoUnitario;
                else
                    price = 0.0;

                // Validamos el stock
                int itemsNumber = Convert.ToInt32(itemNode.Attributes[Constants.TAG_ATT_NAME_ITEM_QUANTITY].Value);

                int itemsNumberAvailable;
                if (storeRoom != null)
                    itemsNumberAvailable  = item.GetStoreRoomInfo(storeRoom.Id).Stock;
                else
                    itemsNumberAvailable = 0;

                
                if (itemsNumber > itemsNumberAvailable)
                {
                    _IsErrorItem = true;
                    // Escribo en archivo que no hay suficiente cantidad
                    this.WriteToFile(logFileName, Constants.MESSAGE_CHECK_NOT_ENOUGH_ITEMS);
                    return null;
                }

                // Validamos el impuesto
                String taxAcronym = itemNode.Attributes[Constants.TAG_ATT_NAME_ITEM_TAX].Value;
                Tax tax;
                if (taxAcronym == Constants.DEFAULT_VALUE)
                    tax = ConfigurationManager.GetInstance().GetDefaultTax();
                else
                    tax = ConfigurationManager.GetInstance().GetTax(taxAcronym);
                if (tax == null)
                {
                    _IsErrorItem = true;
                    // Escribir en el archivo y setear a false para que no haga el doc
                    this.WriteToFile(logFileName, Constants.MESSAGE_CHECK_ITEM_TAX_NOT_FOUND);
                    return null;
                }

                if (!_IsErrorItem)
                {
                    detail = new DocDetail(item, storeRoom, itemsNumber, price, tax);
                    details.Add(detail);
                    this.WriteToFile(logFileName, Constants.MESSAGE_CHECK_ITEM_SUCCESS1 + item.CodArticulo + " " + item.Descripción + Constants.MESSAGE_CHECK_ITEM_SUCCESS2);
                }
            }

            if (_IsErrorItem)
                return null;

            return details;
        }

        /// <summary>
        /// Método que se encarga de escribir en el archivo de resultados el mensaje dado
        /// </summary>
        /// <param name="fileName">Nombre del archivo de resultados</param>
        /// <param name="message">Mensaje a escribir en el archivo</param>
        private void WriteToFile(String fileName, String message)
        {
            //Creating a new stream-writer and opening the file.
            StreamWriter tsw = File.AppendText(fileName);
            
            //Writing text to the file.
            tsw.WriteLine(message + "\n");
            
            //Close the file
            tsw.Close();
        }

        /// <summary>
        /// Método que se encarga de Calcular el monto antes de impuestos
        /// </summary>
        /// <param name="details"></param>
        /// <returns></returns>
        private double CalculateAmountBeforeTax(List<DocDetail> details)
        {
            double amount = 0.0;

            foreach (DocDetail detail in details)
            {
                amount += detail.UnitCost;
            }

            return amount;
        }

        /// <summary>
        /// Método que se encarga de calcular el monto de impuestos
        /// </summary>
        /// <param name="details"></param>
        /// <returns></returns>
        private double CalculateTaxAmount(List<DocDetail> details)
        {
            double amount = 0.0;

            foreach (DocDetail detail in details)
            {
                amount += detail.UnitCost * detail.Tax.Porcentaje;
            }

            return amount;
        }
        #endregion

    }
}
